package com.googlecode.quillen.util;

import java.io.*;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: greg
 * Date: Jun 7, 2009
 * Time: 8:36:52 PM
 */
public class SortedFileList implements Iterable<File>
{
    private static final int BATCH_SIZE = 10000;

    private final String tempFilenamePrefix = TempFileUtils.getPrefix();

    private final Comparator<File> fileComparator = new Comparator<File>()
    {
        public int compare(File left, File right)
        {
            return Long.valueOf(left.length()).compareTo(right.length());
        }
    };

    private List<File> currentBatch;
    private List<File> batches;

    // This utility will build a list of files sorted by size.  If the number of files exceeds BATCH_SIZE, then the batch
    // will be sorted and dumped out to a temp file on disk.  The iterator will then stream from the in-memory batch and
    // from the batches on disk, if necessary, in a merge-sort fashion.
    //
    @SuppressWarnings("unchecked")
    public SortedFileList(Collection<File> paths) throws IOException
    {
        currentBatch = new ArrayList<File>();
        batches = new ArrayList<File>();

        for (File path : paths)
        {
            if (path.exists() && path.isDirectory())
            {
                addFilesInDir(path);
            }
            else if (path.exists() && path.isFile())
            {
                addAndFlush(path);
            }
        }

        sortCurrentBatch();
    }

    private void addFilesInDir(File dir) throws IOException
    {
        for (File f : dir.listFiles())
        {
            if (f.exists())
            {
                if (f.isFile())
                {
                    addAndFlush(f);
                }
                else if (f.isDirectory())
                {
                    addFilesInDir(f);
                }
            }
        }
    }

    private void addAndFlush(File file) throws IOException
    {
        currentBatch.add(file);

        if (currentBatch.size() >= BATCH_SIZE)
        {
            sortCurrentBatch();

            File tempFile = TempFileUtils.createTempFile(tempFilenamePrefix);

            PrintWriter out = new PrintWriter(tempFile);
            try
            {
                for (File f : currentBatch)
                {
                    out.println(f.getAbsolutePath());
                }
            }
            finally
            {
                out.close();
            }

            currentBatch = new ArrayList<File>();
            batches.add(tempFile);
        }
    }

    private void sortCurrentBatch()
    {
        Collections.sort(currentBatch, fileComparator);
    }

    public Iterator<File> iterator()
    {
        try
        {
            return new SortedFileListIterator(this);
        }
        catch (FileNotFoundException e)
        {
            return null;
        }
        catch (IOException e)
        {
            return null;
        }
    }

    public void close()
    {
        TempFileUtils.deleteAll(tempFilenamePrefix);
    }

    public class SortedFileListIterator implements Iterator<File>
    {
        private Iterator<File> inMemoryIter;
        private BufferedReader[] readers;
        private File[] tops;

        public SortedFileListIterator(SortedFileList list) throws IOException
        {
            inMemoryIter = currentBatch.iterator();

            int numBatchesOnDisk = list.batches.size();

            readers = new BufferedReader[numBatchesOnDisk];
            tops = new File[numBatchesOnDisk + 1];

            read(0);

            for (int i = 0; i < numBatchesOnDisk; i++)
            {
                readers[i] = new BufferedReader(new FileReader(list.batches.get(i)));
                read(i + 1);
            }
        }

        private void read(int i) throws IOException
        {
            if (i == 0)
            {
                tops[0] = inMemoryIter.hasNext() ? inMemoryIter.next() : null;
            }
            else
            {
                String line;
                if (readers[i - 1] != null && (line = readers[i - 1].readLine()) != null)
                {
                    tops[i] = new File(line);
                }
                else
                {
                    tops[i] = null;
                    readers[i - 1] = null;
                }
            }
        }

        public File next()
        {
            int min = -1;

            for (int i = 0; i < tops.length; i++)
            {
                if (tops[i] != null && (min < 0 || fileComparator.compare(tops[i], tops[min]) < 0))
                {
                    min = i;
                }
            }

            if (min >= 0)
            {
                File result = tops[min];
                try
                {
                    read(min);
                }
                catch (IOException e)
                {
                    throw new IllegalStateException(e);
                }

                return result;
            }
            else
            {
                return null;
            }
        }

        public boolean hasNext()
        {
            for (File top : tops)
            {
                if (top != null)
                {
                    return true;
                }
            }

            return false;
        }

        public void remove()
        {
            throw new UnsupportedOperationException("cannot call remove on a SortedFileListIterator");
        }
    }
}
